package com.suray.basic.wcs.path.core.api.impl;

import com.suray.basic.wcs.path.core.Lifter;
import com.suray.basic.wcs.path.core.Node;
import com.suray.basic.wcs.path.core.Route;
import com.suray.basic.wcs.path.core.api.DifRouteFactory;
import com.suray.basic.wcs.path.enums.NodeType;
import com.suray.basic.wcs.path.exception.CreatePathFailedException;
import com.suray.basic.wcs.time.bo.WCSTimeModuleBO;
import com.suray.basic.wcs.utils.Coord;
import com.suray.basic.wcs.utils.LockNode;

import java.util.*;

public class DifFloorRouteFactoryImp implements DifRouteFactory {
    private List<Node> map;
    /**
     * 当前库区被锁定的点
     */
    private Set<LockNode> lockedNodes;


    private WCSTimeModuleBO wcsTimeModuleBO;

    private int rgvType;


//    public DifFloorRouteFactoryImp(List<Node> map,Set< LockNode > lockedNodes, WCSTimeModuleBO wcsTimeModuleBO) {
//        this.map = map;
//        this.lockedNodes = lockedNodes;
//        this.wcsTimeModuleBO = wcsTimeModuleBO;
//        this.seerRoute = false;
//    }

    /**
     *
     * @param map
     * @param lockedNodes
     * @param wcsTimeModuleBO
     * @param rgvType ： 如果只想计算路径，不限制小车，类型可以填0
     */
    public DifFloorRouteFactoryImp(List<Node> map,Set< LockNode > lockedNodes, WCSTimeModuleBO wcsTimeModuleBO,
                                   int rgvType) {
        this.map = map;
        this.lockedNodes = lockedNodes;
        this.wcsTimeModuleBO = wcsTimeModuleBO;
        this.rgvType = rgvType;
    }

    public WCSTimeModuleBO getWcsTimeModuleBO() {
        return wcsTimeModuleBO;
    }

    public void setWcsTimeModuleBO(WCSTimeModuleBO wcsTimeModuleBO) {
        this.wcsTimeModuleBO = wcsTimeModuleBO;
    }

    /**
     * 获取之于当前点最近可行驶的提升机
     *
     * @param current  当前点
     * @param endNode  目的点
     * @param lifters  提升机集合
     * @return 最优位置提升机
     * @throws CreatePathFailedException
     */
    private Node getMostAvailableLifter(Node current, Node endNode,Set<Lifter> lifters) throws CreatePathFailedException {
        List<Node> lifterNodes = new ArrayList<>();
        for (Lifter curLifter : lifters) {
            lifterNodes.add(new Node(curLifter.getX(), curLifter.getY(), current.getZ()));
        }
        RouteFactoryImp routeFactoryStartFloor = new RouteFactoryImp(getMapByLayer(current.getZ()),lockedNodes,
                wcsTimeModuleBO, rgvType);
        RouteFactoryImp routeFactoryEndFloor = new RouteFactoryImp(getMapByLayer(endNode.getZ()),lockedNodes,
                wcsTimeModuleBO, rgvType);
        Node lifterNode = new NodeChooseImpl(routeFactoryStartFloor, routeFactoryEndFloor).getMostAccessibleNode(current, endNode, lifterNodes);
        if (lifterNode == null) {
            throw new CreatePathFailedException(current, endNode, "未找到可使用提升机!");
        }
        return lifterNode;
    }

    /**
     * 跨层路径规划
     *
     * @param start        起点
     * @param destination  目的点
     * @param palletStatus 托板状态
     */
    @Override
    public List<Route> createRoute(Node start, Node destination, boolean palletStatus)
            throws IllegalArgumentException, IllegalStateException, CreatePathFailedException {
        List<Route> routes = new ArrayList<>();
        RouteFactoryImp routeFactoryStartFloor = new RouteFactoryImp(getMapByLayer(start.getZ()),lockedNodes,
                wcsTimeModuleBO, rgvType);
        if (start.getZ() == destination.getZ()) {
            routes.add(routeFactoryStartFloor.createRoute(start, destination, palletStatus));
            return routes;
        }
        Set<Lifter> liftersEndLayer = getLifters(getMapByLayer(destination.getZ()));
        Set<Lifter> liftersStartLayer = getLifters(getMapByLayer(start.getZ()));
        liftersEndLayer.addAll(liftersStartLayer);
        Node lift = getMostAvailableLifter(start, destination, liftersEndLayer);
        Route route1 = routeFactoryStartFloor.createRoute(start, new Node(lift.getX(), lift.getY(), start.getZ()), palletStatus);
        RouteFactoryImp routeFactoryDestinationFloor = new RouteFactoryImp(getMapByLayer(destination.getZ()),
                lockedNodes, wcsTimeModuleBO, rgvType);
        Route route2 = routeFactoryDestinationFloor.createRoute(new Node(lift.getX(), lift.getY(), destination.getZ()),
                destination, palletStatus);
        routes.add(route1);
        routes.add(route2);
        Long cost;
        if(Long.MAX_VALUE - crossLayerCost < route2.getCost().intValue()) {
            cost = Long.MAX_VALUE;
        } else {
            cost = route2.getCost() + crossLayerCost;
        }
        route2.setCost(cost);
        return routes;
    }

    /**
     * 获取所有提升机
     *
     * @param nodes 仓库某个平面的所有点
     */
    public Set<Lifter> getLifters(List<Node> nodes) {
        Set<Lifter> lifterSet = new HashSet<>();
        for (Node node : nodes) {
            if (node.getNodeType() == NodeType.L) {
                lifterSet.add(new Lifter(node.getX(), node.getY()));
            }
        }
        return lifterSet;
    }

    private static final int crossLayerCost = 100000;

    /**
     * 获取某一层地图数据
     *
     * @param layer
     * @return
     */
    public List<Node> getMapByLayer(int layer) {
        List<Node> nodes = new ArrayList<>();
        for (Node node : map) {
            if (node.getZ() == layer) {
                //节点初始化，防止之前存在的属性干扰
                node.parent = null;
                node.G = 0;
                node.H = 0;
                nodes.add(node);
            }
        }
        return nodes;
    }

    public Node getLatelyNode(Node startNode, List<Coord> availableNodeList, boolean palletStatus) throws CreatePathFailedException {
        List<Node> avoidNodes = new ArrayList<>();
        if(availableNodeList.size() < 1) {
            return null;
        }
        Map<String, List<Coord>> coordsMap = new HashMap<>();
        for (Coord coord : availableNodeList) {
            List<Coord> coords = coordsMap.get(String.valueOf(coord.getZ()));
            if(coords == null) {
                coords = new ArrayList<>();
                coordsMap.put(String.valueOf(coord.getZ()), coords);
            }
            coords.add(coord);
        }
        for (Map.Entry<String, List<Coord>> entry : coordsMap.entrySet()) {
            int targetLayer = Integer.valueOf(entry.getKey());
            List<Coord> coords = entry.getValue();
            RouteFactoryImp routeFactoryStartFloor = new RouteFactoryImp(getMapByLayer(startNode.getZ()),lockedNodes,
                    wcsTimeModuleBO, rgvType);
            if (startNode.getZ() == targetLayer) {
                Node avoidNode = routeFactoryStartFloor.getLatelyNode(startNode, coords, palletStatus);
                if(avoidNode != null) {
                    avoidNode.G = startNode.G;
                    avoidNodes.add(avoidNode);
                }
                continue;
            }
            Set<Lifter> lifters = getLifters(getMapByLayer(targetLayer));
            Node lift = getMostAvailableLifter(startNode, new Node(coords.get(0)), lifters);
            try {
                Route route1 = routeFactoryStartFloor.createRoute(startNode, new Node(lift.getX(), lift.getY(), startNode.getZ()), palletStatus);
                RouteFactoryImp routeFactoryDestinationFloor = new RouteFactoryImp(getMapByLayer(targetLayer),
                        lockedNodes, wcsTimeModuleBO, rgvType);
                Node start = new Node(lift.getX(), lift.getY(), targetLayer);
                Node avoidNode = routeFactoryDestinationFloor.getLatelyNode(start, coords, palletStatus);
                if(avoidNode != null) {
                    avoidNode.G = start.G;
                    avoidNodes.add(avoidNode);
                }
            } catch (Exception e) {}
        }
        if(avoidNodes.size() == 1) {
            return avoidNodes.get(0);
        } else if(avoidNodes.size() > 1) {
            long minValue = avoidNodes.get(0).G;
            int minIndex = 0;
            for (int i = 0; i < avoidNodes.size(); i++) {
                if (i == avoidNodes.size() - 1) {
                    continue;
                }
                long next = avoidNodes.get(i + 1).G;
                if (minValue > next) {
                    minValue = next;
                    minIndex = i;
                }
            }
            return avoidNodes.get(minIndex);
        }
        return null;
    }
}
